/**
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "compiler/optimizer/code_generator/codegen.h"
#include "plugins/ecmascript/compiler/ecmascript_extensions/ecmascript_environment.h"
#include "runtime/include/hclass.h"
#include "runtime/include/thread.h"
#include "plugins/ecmascript/runtime/mem/tagged_object.h"

namespace ark::compiler {

// NOTE(vpukhov): reuse irtoc iframe reg
static void LoadIFramePtr(Codegen *cg, Reg dst)
{
    cg->GetEncoder()->EncodeLdr(dst, false,
                                MemRef(cg->ThreadReg(), cross_values::GetManagedThreadFrameOffset(cg->GetArch())));
}

static MemRef IFrameAccMemRef(Encoder *enc, Reg iframeReg)
{
    return MemRef(iframeReg, cross_values::GetFrameAccOffset(enc->GetArch()));
}

MemRef AccMemRef(Encoder *enc, Reg thread, Reg accReg)
{
    enc->EncodeAdd(accReg, thread, Imm(ManagedThread::GetFrameOffset()));
    auto accPtr = MemRef(accReg);
    enc->EncodeLdr(accReg, false, MemRef(accPtr));
    return MemRef(accReg, cross_values::GetFrameAccOffset(enc->GetArch()));
}

void Codegen::LdlexenvDyn([[maybe_unused]] IntrinsicInst *inst, [[maybe_unused]] Reg dst, [[maybe_unused]] SRCREGS src)
{
    GetEncoder()->EncodeLdr(dst, false, MemRef(src[0], cross_values::GetJsfunctionLexicalEnvOffset(GetArch())));
}

static void EncodeLoadParentLexEnv(Codegen *cg, IntrinsicInst *inst, uint32_t levelImm, Reg levelReg, Reg lexEnv,
                                   Reg lexEnvPtr)
{
    auto *enc = cg->GetEncoder();
    auto arch = enc->GetArch();
    auto *runtime = cg->GetRuntime();

    auto data = runtime->GetArrayDataOffset(arch);
    auto elemSize = runtime->GetTaggedArrayElementSize();
    if (!inst->HasImms() || levelImm > 0U) {
        auto head = enc->CreateLabel();
        auto exit = enc->CreateLabel();
        ScopedTmpRegU32 counter(enc);
        if (inst->HasImms()) {
            enc->EncodeMov(counter, Imm(levelImm));
        } else {
            enc->EncodeJump(exit, levelReg.As(INT32_TYPE), Condition::EQ);  // fast path for level 0
            enc->EncodeMov(counter, levelReg.As(INT32_TYPE));
        }
        enc->BindLabel(head);
        // get parent env
        enc->EncodeLdr(lexEnv, false, MemRef(lexEnvPtr, data + runtime->GetLexicalEnvParentEnvIndex() * elemSize));
        enc->EncodeSub(counter, counter, Imm(1U));
        enc->EncodeJump(head, counter, Condition::NE);
        enc->BindLabel(exit);
    }
}

void Codegen::EncodeGetUnmappedArgs(IntrinsicInst *inst, [[maybe_unused]] Reg dst, [[maybe_unused]] SRCREGS src)
{
    auto *enc = GetEncoder();
    auto slot = GetRuntime()->GetNumMandatoryArgs();
    auto offset = GetStackOffset(Location(LocationType::STACK_PARAMETER, slot));
    Reg param1 = GetTarget().GetParamReg(1);
    enc->EncodeAdd(param1, SpReg(), Imm(offset));
    CreateCallIntrinsic(inst);
}

void Codegen::LdLexVarDyn(IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    ASSERT(inst->HasImms() == (inst->GetInputsCount() == 1U));
    ASSERT(GetGraph()->GetMode().IsInterpreter() != inst->HasImms());

    auto *enc = GetEncoder();
    auto arch = enc->GetArch();
    auto *runtime = GetRuntime();

    ScopedTmpRegU64 tmp(enc);
    auto lexEnv = tmp.GetReg().As(Codegen::ConvertDataType(DataType::ANY, arch));
    auto lexEnvPtr = tmp.GetReg().As(Codegen::ConvertDataType(DataType::POINTER, arch));
    ASSERT(lexEnv.GetSize() >= lexEnvPtr.GetSize());

    ASSERT(GetGraph()->SupportManagedCode() == (inst->GetSaveState() == nullptr));
    std::size_t lexEnvIndex = GetGraph()->GetMode().IsInterpreter() ? 2 : 0;
    enc->EncodeMov(tmp, src[lexEnvIndex]);

    EncodeLoadParentLexEnv(this, inst, inst->HasImms() ? inst->GetImm(0) : 0, src[0], lexEnv, lexEnvPtr);

    auto data = runtime->GetArrayDataOffset(arch);
    auto elemSize = runtime->GetTaggedArrayElementSize();
    auto start = runtime->GetLexicalEnvStartDataIndex();
    constexpr size_t SLOT_INDEX = 1U;
    if (!GetGraph()->GetMode().IsInterpreter()) {
        enc->EncodeLdr(dst, false, MemRef(lexEnvPtr, data + (start + inst->GetImms()[SLOT_INDEX]) * elemSize));
    } else {
        auto props = tmp.GetReg().As(Codegen::ConvertDataType(DataType::ANY, arch));
        enc->EncodeLdr(props, false,
                       MemRef(lexEnvPtr, src[SLOT_INDEX].As(INT32_TYPE), elemSize, data + start * elemSize));

        // save to acc
        ScopedTmpReg iframeReg(enc, Codegen::ConvertDataType(DataType::POINTER, arch));
        LoadIFramePtr(this, iframeReg);
        enc->EncodeStr(props, IFrameAccMemRef(enc, iframeReg));
    }
}

void Codegen::StLexVarDyn(IntrinsicInst *inst, [[maybe_unused]] Reg dst, SRCREGS src)
{
    ASSERT(inst->HasImms() == (inst->GetInputsCount() == 2U));
    ASSERT(GetGraph()->GetMode().IsInterpreter() != inst->HasImms());

    auto *enc = GetEncoder();
    auto arch = enc->GetArch();
    auto *runtime = GetRuntime();

    ScopedTmpRegU64 tmp(enc);
    auto lexEnv = tmp.GetReg().As(Codegen::ConvertDataType(DataType::ANY, arch));
    auto lexEnvPtr = tmp.GetReg().As(Codegen::ConvertDataType(DataType::POINTER, arch));
    ASSERT(lexEnv.GetSize() >= lexEnvPtr.GetSize());

    ASSERT(GetGraph()->SupportManagedCode() == (inst->GetSaveState() == nullptr));
    std::size_t lexEnvIndex = GetGraph()->GetMode().IsInterpreter() ? 3 : 1;
    enc->EncodeMov(tmp, src[lexEnvIndex]);

    EncodeLoadParentLexEnv(this, inst, inst->HasImms() ? inst->GetImm(0) : 0, src[0], lexEnv, lexEnvPtr);

    auto data = runtime->GetArrayDataOffset(arch);
    auto elemSize = runtime->GetTaggedArrayElementSize();
    auto start = runtime->GetLexicalEnvStartDataIndex();
    constexpr size_t SLOT_INDEX = 1U;
    std::size_t accIndex = GetGraph()->GetMode().IsInterpreter() ? 2 : 0;
    if (!GetGraph()->GetMode().IsInterpreter()) {
        enc->EncodeStr(src[accIndex], MemRef(lexEnvPtr, data + (start + inst->GetImms()[SLOT_INDEX]) * elemSize));
    } else {
        enc->EncodeStr(src[accIndex],
                       MemRef(lexEnvPtr, src[SLOT_INDEX].As(INT32_TYPE), elemSize, data + start * elemSize));
    }
}

void Codegen::StLexDyn(IntrinsicInst *inst, [[maybe_unused]] Reg dst, SRCREGS src)
{
    ASSERT(inst->HasImms() == (inst->GetInputsCount() == 4U));
    ASSERT(GetGraph()->GetMode().IsInterpreter() != inst->HasImms());

    if (GetGraph()->GetMode().IsInterpreter()) {
        CreateCallIntrinsic(inst);
        return;
    }

    auto *enc = GetEncoder();
    auto arch = enc->GetArch();
    auto *runtime = GetRuntime();

    ScopedTmpRegU64 tmp(enc);
    auto lexEnv = tmp.GetReg().As(Codegen::ConvertDataType(DataType::ANY, arch));
    auto lexEnvPtr = tmp.GetReg().As(Codegen::ConvertDataType(DataType::POINTER, arch));
    ASSERT(lexEnv.GetSize() >= lexEnvPtr.GetSize());

    std::size_t lexEnvIndex = GetGraph()->GetMode().IsInterpreter() ? 4 : 1;
    enc->EncodeMov(tmp, src[lexEnvIndex]);

    EncodeLoadParentLexEnv(this, inst, inst->HasImms() ? inst->GetImm(1) : 0, src[1], lexEnv, lexEnvPtr);

    auto data = runtime->GetArrayDataOffset(arch);
    auto elemSize = runtime->GetTaggedArrayElementSize();
    auto start = runtime->GetLexicalEnvStartDataIndex();
    constexpr size_t SLOT_INDEX = 1U;
    auto memRef = MemRef(lexEnvPtr, data + (start + inst->GetImms()[SLOT_INDEX]) * elemSize);

    ScopedTmpReg tmpReg(enc, ConvertDataType(DataType::REFERENCE, arch));
    enc->EncodeLdr(tmpReg, false, memRef);
    auto slowPath = CreateSlowPath<SlowPathDeoptimize>(inst, DeoptimizeType::HOLE);
    enc->EncodeJump(slowPath->GetLabel(), tmpReg, Imm(ark::coretypes::TaggedValue::VALUE_HOLE), Condition::EQ);

    std::size_t accIndex = 0;
    enc->EncodeStr(src[accIndex], memRef);
}

void Codegen::GetObjectClassTypeIntrinsic([[maybe_unused]] IntrinsicInst *inst, [[maybe_unused]] Reg dst, SRCREGS src)
{
    ScopedTmpReg tmpReg(GetEncoder(), ConvertDataType(DataType::UINT64, GetArch()));
    Reg tmpRegRef = tmpReg.GetReg().As(ConvertDataType(DataType::REFERENCE, GetArch()));

    GetEncoder()->EncodeLdr(tmpRegRef, false, MemRef(src[0], GetRuntime()->GetObjClassOffset(GetArch())));
    GetEncoder()->EncodeLdr(tmpReg, false, MemRef(tmpRegRef, cross_values::GetJshclassBitfieldOffset(GetArch())));
    GetEncoder()->EncodeAnd(tmpReg, tmpReg,
                            Imm(static_cast<uint64_t>(cross_values::GetJshclassBitfieldTypeMask(GetArch()))));
    auto typeStartBit = cross_values::GetJshclassBitfieldTypeStartBit(GetArch());
    if (typeStartBit != 0) {
        GetEncoder()->EncodeShr(tmpReg, tmpReg, Imm(typeStartBit));
    }
    GetEncoder()->EncodeMov(dst, tmpReg);
}

void Codegen::GetWeakReferent([[maybe_unused]] IntrinsicInst *inst, [[maybe_unused]] Reg dst,
                              [[maybe_unused]] SRCREGS src)
{
    GetEncoder()->EncodeMov(dst, src[0]);
    GetEncoder()->EncodeAnd(dst, dst, Imm(~TaggedValue::TAG_WEAK_MASK));
}

void Codegen::CreateDynClassIsDictionaryElement([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    ScopedTmpReg tmpReg(GetEncoder(), ConvertDataType(DataType::UINT64, GetArch()));
    GetEncoder()->EncodeLdr(tmpReg, false, MemRef(src[0], cross_values::GetJshclassBitfieldOffset(GetArch())));
    GetEncoder()->EncodeShr(tmpReg, tmpReg, Imm(cross_values::GetJshclassBitfieldIsDictionaryStartBit(GetArch())));
    GetEncoder()->EncodeAnd(dst, tmpReg, Imm(1U));
}

void Codegen::CreateDynClassIsExtensible([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    ScopedTmpReg tmpReg(GetEncoder(), ConvertDataType(DataType::UINT64, GetArch()));
    GetEncoder()->EncodeLdr(tmpReg, false, MemRef(src[0], cross_values::GetJshclassBitfieldOffset(GetArch())));
    GetEncoder()->EncodeShr(tmpReg, tmpReg, Imm(cross_values::GetJshclassBitfieldExtensibleStartBit(GetArch())));
    GetEncoder()->EncodeAnd(dst, tmpReg, Imm(1U));
}

void Codegen::CreateDynObjectGetClass(IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    LoadClassFromObject(dst, src[0]);
    GetEncoder()->EncodeSub(dst, dst, Imm(cross_values::GetJshclassHclassOffset(GetArch())));
    if (inst->GetSaveState() != nullptr) {
        CreateStackMap(inst);
    }
}

void Codegen::CreateDynObjectSetClass(IntrinsicInst *inst, [[maybe_unused]] Reg dst, SRCREGS src)
{
    ScopedTmpReg tmpReg(GetEncoder(), ConvertDataType(DataType::REFERENCE, GetArch()));
    GetEncoder()->EncodeMov(tmpReg, src[1]);
    GetEncoder()->EncodeAdd(tmpReg, tmpReg, Imm(cross_values::GetJshclassHclassOffset(GetArch())));
    GetEncoder()->EncodeStr(tmpReg, MemRef(src[0], GetRuntime()->GetObjClassOffset(GetArch())));
    if (inst->GetSaveState() != nullptr) {
        CreateStackMap(inst);
    }
}

void Codegen::CreateDynClassNumberOfProps([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    ScopedTmpReg tmpReg(GetEncoder(), ConvertDataType(DataType::UINT64, GetArch()));
    GetEncoder()->EncodeLdr(tmpReg, false, MemRef(src[0], cross_values::GetJshclassBitfield1Offset(GetArch())));
    GetEncoder()->EncodeShr(tmpReg, tmpReg,
                            Imm(cross_values::GetJshclassBitfield1NumberOfPropsBitsStartBit(GetArch())));
    GetEncoder()->EncodeAnd(dst, tmpReg, Imm(cross_values::GetJshclassBitfield1NumberOfPropsBitsMask(GetArch())));
}

void Codegen::CreateDynClassGetHash([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    GetEncoder()->EncodeMov(dst, src[0].As(TypeInfo(TypeInfo::TypeId::INT32)));
    GetEncoder()->EncodeShr(dst, dst, Imm(3U));
}

void Codegen::CreateLdObjDynByName([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    CallFastPath(inst, EntrypointId::LOAD_OBJECT_DYNAMIC_BY_NAME, dst, {}, src[0], src[1U], src[2U], src[3U]);
}

void Codegen::CreateStObjDynByName([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    CallFastPath(inst, EntrypointId::STORE_OBJECT_DYNAMIC_BY_NAME, dst, {}, src[0], src[1U], src[2U], src[3U], src[4U]);
}

void Codegen::CreateAllocDynObject([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    CallFastPath(inst, EntrypointId::ALLOC_DYN_OBJECT_STUB, dst, {}, src[0]);
}

void Codegen::CreateResolveAllocResult([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    CallFastPath(inst, EntrypointId::RESOLVE_CTOR_RESULT, dst, {}, src[0], src[1U], src[2U]);
}

void Codegen::CreateEcmaStringEquals([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    auto entrypointId = GetRuntime()->IsCompressedStringsEnabled() ? EntrypointId::ECMA_STRING_EQUALS_COMPRESSED
                                                                   : EntrypointId::ECMA_STRING_EQUALS;
    CallFastPath(inst, entrypointId, dst, {}, src[0], src[1U]);
}

void Codegen::CreateFastPathStrictEq([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    auto entrypointId = EntrypointId::FAST_PATH_ECMA_STRICT_EQ;
    CallFastPath(inst, entrypointId, dst, {}, src[0], src[1U]);
    GetEncoder()->EncodeOr(dst, dst, Imm(ark::coretypes::TaggedValue::VALUE_FALSE));
}

void Codegen::CreateFastPathStrictNotEq([[maybe_unused]] IntrinsicInst *inst, Reg dst, SRCREGS src)
{
    auto entrypointId = EntrypointId::FAST_PATH_ECMA_STRICT_EQ;
    CallFastPath(inst, entrypointId, dst, {}, src[0], src[1U]);
    GetEncoder()->EncodeXor(dst, dst, Imm(1));
    GetEncoder()->EncodeOr(dst, dst, Imm(ark::coretypes::TaggedValue::VALUE_FALSE));
}

}  // namespace ark::compiler
